/**
 * Copyright (c) 20011-2017 Bill Greiman
 * This file is part of the SdFat library for SD memory cards.
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#ifndef FatVolume_h
#define FatVolume_h
/**
 * \file
 * \brief FatVolume class
 */
#include <stddef.h>
#include "FatLibConfig.h"
#include "FatStructs.h"
#include "../BlockDriver.h"
//------------------------------------------------------------------------------
#ifndef DOXYGEN_SHOULD_SKIP_THIS
/** Macro for debug. */
#define DEBUG_MODE 0
#if DEBUG_MODE
#define DBG_FAIL_MACRO \
    Serial.print(F(__FILE__)); \
    Serial.println(__LINE__);
#define DBG_PRINT_IF(b) \
    if (b) { \
        Serial.println(F(#b)); \
        DBG_FAIL_MACRO; \
    }
#define DBG_HALT_IF(b) \
    if (b) { \
        Serial.println(F(#b)); \
        DBG_FAIL_MACRO; \
        while (1) \
            ; \
    }
#else // DEBUG_MODE
#define DBG_FAIL_MACRO
#define DBG_PRINT_IF(b)
#define DBG_HALT_IF(b)
#endif // DEBUG_MODE
#endif // DOXYGEN_SHOULD_SKIP_THIS
//------------------------------------------------------------------------------
#if ENABLE_ARDUINO_FEATURES
/** Use Print for Arduino */
// typedef Print print_t;
#else  // ENABLE_ARDUINO_FEATURES
/**
 * \class CharWriter
 * \brief Character output - often serial port.
 */
/*class CharWriter {
 public:
  virtual size_t write(char c) = 0;
  virtual size_t write(const char* s) = 0;
};
typedef CharWriter print_t;*/
#endif // ENABLE_ARDUINO_FEATURES
//------------------------------------------------------------------------------
// Forward declaration of FatVolume.
class FatVolume;
//------------------------------------------------------------------------------
/**
 * \brief Cache for an raw data block.
 */
union cache_t {
    /** Used to access cached file data blocks. */
    uint8_t data[512];
    /** Used to access cached FAT16 entries. */
    uint16_t fat16[256];
    /** Used to access cached FAT32 entries. */
    uint32_t fat32[128];
    /** Used to access cached directory entries. */
    dir_t dir[16];
    /** Used to access a cached Master Boot Record. */
    mbr_t mbr;
    /** Used to access to a cached FAT boot sector. */
    fat_boot_t fbs;
    /** Used to access to a cached FAT32 boot sector. */
    fat32_boot_t fbs32;
    /** Used to access to a cached FAT32 FSINFO sector. */
    fat32_fsinfo_t fsinfo;
};
//==============================================================================
/**
 * \class FatCache
 * \brief Block cache.
 */
class FatCache {
public:
    /** Cached block is dirty */
    static const uint8_t CACHE_STATUS_DIRTY = 1;
    /** Cashed block is FAT entry and must be mirrored in second FAT. */
    static const uint8_t CACHE_STATUS_MIRROR_FAT = 2;
    /** Cache block status bits */
    static const uint8_t CACHE_STATUS_MASK
        = CACHE_STATUS_DIRTY | CACHE_STATUS_MIRROR_FAT;
    /** Sync existing block but do not read new block. */
    static const uint8_t CACHE_OPTION_NO_READ = 4;
    /** Cache block for read. */
    static const uint8_t CACHE_FOR_READ = 0;
    /** Cache block for write. */
    static const uint8_t CACHE_FOR_WRITE = CACHE_STATUS_DIRTY;
    /** Reserve cache block for write - do not read from block device. */
    static const uint8_t CACHE_RESERVE_FOR_WRITE
        = CACHE_STATUS_DIRTY | CACHE_OPTION_NO_READ;
    /** \return Cache block address. */
    cache_t* block() {
        return &m_block;
    }
    /** Set current block dirty. */
    void dirty() {
        m_status |= CACHE_STATUS_DIRTY;
    }
    /** Initialize the cache.
   * \param[in] vol FatVolume that owns this FatCache.
   */
    void init(FatVolume* vol) {
        m_vol = vol;
        invalidate();
    }
    /** Invalidate current cache block. */
    void invalidate() {
        m_status = 0;
        m_lbn = 0XFFFFFFFF;
    }
    /** \return dirty status */
    bool isDirty() {
        return m_status & CACHE_STATUS_DIRTY;
    }
    /** \return Logical block number for cached block. */
    uint32_t lbn() {
        return m_lbn;
    }
    /** Read a block into the cache.
   * \param[in] lbn Block to read.
   * \param[in] option mode for cached block.
   * \return Address of cached block. */
    cache_t* read(uint32_t lbn, uint8_t option);
    /** Write current block if dirty.
   * \return true for success else false.
   */
    bool sync();

private:
    uint8_t m_status;
    FatVolume* m_vol;
    uint32_t m_lbn;
    cache_t m_block;
};
//==============================================================================
/**
 * \class FatVolume
 * \brief Access FAT16 and FAT32 volumes on raw file devices.
 */
class FatVolume {
public:
    /** Create an instance of FatVolume
   */
    FatVolume()
        : m_fatType(0) {}

    /** \return The volume's cluster size in blocks. */
    uint8_t blocksPerCluster() const {
        return m_blocksPerCluster;
    }
    /** \return The number of blocks in one FAT. */
    uint32_t blocksPerFat() const {
        return m_blocksPerFat;
    }
    /** Clear the cache and returns a pointer to the cache.  Not for normal apps.
   * \return A pointer to the cache buffer or zero if an error occurs.
   */
    cache_t* cacheClear() {
        if (!cacheSync()) {
            return 0;
        }
        m_cache.invalidate();
        return m_cache.block();
    }
    /** \return The total number of clusters in the volume. */
    uint32_t clusterCount() const {
        return m_lastCluster - 1;
    }
    /** \return The shift count required to multiply by blocksPerCluster. */
    uint8_t clusterSizeShift() const {
        return m_clusterSizeShift;
    }
    /** \return The logical block number for the start of file data. */
    uint32_t dataStartBlock() const {
        return m_dataStartBlock;
    }
    /** \return The number of File Allocation Tables. */
    uint8_t fatCount() {
        return 2;
    }
    /** \return The logical block number for the start of the first FAT. */
    uint32_t fatStartBlock() const {
        return m_fatStartBlock;
    }
    /** \return The FAT type of the volume. Values are 12, 16 or 32. */
    uint8_t fatType() const {
        return m_fatType;
    }
    /** Volume free space in clusters.
   *
   * \return Count of free clusters for success or -1 if an error occurs.
   */
    int32_t freeClusterCount();
    /** Initialize a FAT volume.  Try partition one first then try super
   * floppy format.
   *
   * \return The value true is returned for success and
   * the value false is returned for failure.
   */
    bool init() {
        return init(1) || init(0);
    }
    /** Initialize a FAT volume.

   * \param[in] part The partition to be used.  Legal values for \a part are
   * 1-4 to use the corresponding partition on a device formatted with
   * a MBR, Master Boot Record, or zero if the device is formatted as
   * a super floppy with the FAT boot sector in block zero.
   *
   * \return The value true is returned for success and
   * the value false is returned for failure.
   */
    bool init(uint8_t part);
    /** \return The number of entries in the root directory for FAT16 volumes. */
    uint16_t rootDirEntryCount() const {
        return m_rootDirEntryCount;
    }
    /** \return The logical block number for the start of the root directory
       on FAT16 volumes or the first cluster number on FAT32 volumes. */
    uint32_t rootDirStart() const {
        return m_rootDirStart;
    }
    /** \return The number of blocks in the volume */
    uint32_t volumeBlockCount() const {
        return blocksPerCluster() * clusterCount();
    }
    /** Wipe all data from the volume.
   * \param[in] pr print stream for status dots.
   * \return true for success else false.
   */
    bool wipe();
    /** Debug access to FAT table
   *
   * \param[in] n cluster number.
   * \param[out] v value of entry
   * \return -1 error, 0 EOC, else 1.
   */
    int8_t dbgFat(uint32_t n, uint32_t* v) {
        return fatGet(n, v);
    }
    //------------------------------------------------------------------------------
private:
    // Allow FatFile and FatCache access to FatVolume private functions.
    friend class FatCache;
    friend class FatFile;
    friend class FatFileSystem;
    //------------------------------------------------------------------------------
    BlockDriver* m_blockDev;      // block device
    uint8_t m_blocksPerCluster;   // Cluster size in blocks.
    uint8_t m_clusterBlockMask;   // Mask to extract block of cluster.
    uint8_t m_clusterSizeShift;   // Cluster count to block count shift.
    uint8_t m_fatType;            // Volume type (12, 16, OR 32).
    uint16_t m_rootDirEntryCount; // Number of entries in FAT16 root dir.
    uint32_t m_allocSearchStart;  // Start cluster for alloc search.
    uint32_t m_blocksPerFat;      // FAT size in blocks
    uint32_t m_dataStartBlock;    // First data block number.
    uint32_t m_fatStartBlock;     // Start block for first FAT.
    uint32_t m_lastCluster;       // Last cluster number in FAT.
    uint32_t m_rootDirStart;      // Start block for FAT16, cluster for FAT32.
                                  //------------------------------------------------------------------------------
    // block I/O functions.
    bool readBlock(uint32_t block, uint8_t* dst) {
        return m_blockDev->readBlock(block, dst);
    }
    bool syncBlocks() {
        return m_blockDev->syncBlocks();
    }
    bool writeBlock(uint32_t block, const uint8_t* src) {
        return m_blockDev->writeBlock(block, src);
    }
#if USE_MULTI_BLOCK_IO
    bool readBlocks(uint32_t block, uint8_t* dst, size_t nb) {
        return m_blockDev->readBlocks(block, dst, nb);
    }
    bool writeBlocks(uint32_t block, const uint8_t* src, size_t nb) {
        return m_blockDev->writeBlocks(block, src, nb);
    }
#endif // USE_MULTI_BLOCK_IO
#if MAINTAIN_FREE_CLUSTER_COUNT
    int32_t m_freeClusterCount; // Count of free clusters in volume.
    void setFreeClusterCount(int32_t value) {
        m_freeClusterCount = value;
    }
    void updateFreeClusterCount(int32_t change) {
        if (m_freeClusterCount >= 0) {
            m_freeClusterCount += change;
        }
    }
#else  // MAINTAIN_FREE_CLUSTER_COUNT
    void setFreeClusterCount(int32_t value) {
        (void)value;
    }
    void updateFreeClusterCount(int32_t change) {
        (void)change;
    }
#endif // MAINTAIN_FREE_CLUSTER_COUNT

    // block caches
    FatCache m_cache;
#if USE_SEPARATE_FAT_CACHE
    FatCache m_fatCache;
    cache_t* cacheFetchFat(uint32_t blockNumber, uint8_t options) {
        return m_fatCache.read(blockNumber,
                               options | FatCache::CACHE_STATUS_MIRROR_FAT);
    }
    bool cacheSync() {
        return m_cache.sync() && m_fatCache.sync() && syncBlocks();
    }
#else  //
    cache_t* cacheFetchFat(uint32_t blockNumber, uint8_t options) {
        return cacheFetchData(blockNumber,
                              options | FatCache::CACHE_STATUS_MIRROR_FAT);
    }
    bool cacheSync() {
        return m_cache.sync() && syncBlocks();
    }
#endif // USE_SEPARATE_FAT_CACHE
    cache_t* cacheFetchData(uint32_t blockNumber, uint8_t options) {
        return m_cache.read(blockNumber, options);
    }
    void cacheInvalidate() {
        m_cache.invalidate();
    }
    bool cacheSyncData() {
        return m_cache.sync();
    }
    cache_t* cacheAddress() {
        return m_cache.block();
    }
    uint32_t cacheBlockNumber() {
        return m_cache.lbn();
    }
    void cacheDirty() {
        m_cache.dirty();
    }
    //------------------------------------------------------------------------------
    bool allocateCluster(uint32_t current, uint32_t* next);
    bool allocContiguous(uint32_t count, uint32_t* firstCluster);
    uint8_t blockOfCluster(uint32_t position) const {
        return (position >> 9) & m_clusterBlockMask;
    }
    uint32_t clusterFirstBlock(uint32_t cluster) const;
    int8_t fatGet(uint32_t cluster, uint32_t* value);
    bool fatPut(uint32_t cluster, uint32_t value);
    bool fatPutEOC(uint32_t cluster) {
        return fatPut(cluster, 0x0FFFFFFF);
    }
    bool freeChain(uint32_t cluster);
    bool isEOC(uint32_t cluster) const {
        return cluster > m_lastCluster;
    }
};
#endif // FatVolume
